package com.example.redis.aop;

import cn.hutool.crypto.SecureUtil;
import com.example.redis.annotation.RedisDistributedLock;
import com.example.redis.handle.RedisDistributedLockHandle;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.LinkedList;

/**
 * @author xianxin
 * @since 2020/8/21
 */
@Order(0)
@Slf4j
@Aspect
@Component
public class RedisDistributedLockAspect {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Around("@annotation(redisDistributedLock)")
    public void process(ProceedingJoinPoint joinPoint, RedisDistributedLock redisDistributedLock) throws Throwable {

        RedisDistributedLockHandle handle = new RedisDistributedLockHandle(redisTemplate);

        String lockKey = getLockKey(joinPoint, redisDistributedLock.lockField());

        String lockValue = "VALUE:" + redisDistributedLock.lockExpireTime() + ":" + lockKey;

        // 加锁
        Boolean result = handle.lock(lockKey, lockValue, redisDistributedLock.lockExpireTime());
        if (result) {
            joinPoint.proceed();
            // 解锁
            handle.unlock(lockKey, lockValue);
        } else {
            log.info("分布式锁拦截不能重复操作[KEY={}]", lockKey);
        }
    }

    private String getLockKey(ProceedingJoinPoint joinPoint, String[] lockField) throws Exception {
        LinkedList<String> all = new LinkedList<>();

        Object[] args = joinPoint.getArgs();

        for (int i = 0; i < args.length; i++) {
            Object object = args[i];
            Class<?> clazz = object.getClass();

            if (isJavaClass(clazz)) {
                Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
                Parameter[] parameters = method.getParameters();
                String name = parameters[i].getName();
                for (int i1 = 0; i1 < lockField.length; i1++) {
                    if (name.equals(lockField[i1])) {
                        all.add(String.valueOf(object));
                        break;
                    }
                }
            } else {
                LinkedList<String> linkedList = getFieldValuesByEntity(object, lockField);
                if (!CollectionUtils.isEmpty(linkedList)) {
                    all.addAll(linkedList);
                }
            }
        }
        String lockKey = "";
        for (String s : all) {
            lockKey += s;
        }
        lockKey = SecureUtil.md5(lockKey).toUpperCase();
        return lockKey;
    }

    private LinkedList<String> getFieldValuesByEntity(Object object, String[] lockField) throws Exception {
        LinkedList<String> linkedList = new LinkedList<>();
        Class<?> clazz = object.getClass();

        for (int i = 0; i < lockField.length; i++) {

            String name = getMethodName(lockField[i]);

            Method method = clazz.getDeclaredMethod(name);

            String result = (String) method.invoke(object);

            linkedList.add(result);
        }
        return linkedList;
    }

    /**
     * 获取字段的get方法
     *
     * @param name 字段名
     * @return 字段的get方法全称
     */
    private String getMethodName(String name) {
        char[] firsts = new char[1];
        char firstChar = name.charAt(0);
        firsts[0] = firstChar;
        String firstStr = new String(firsts);
        String firstStrUp = firstStr.toUpperCase();
        String nameSub = name.substring(1);
        return "get" + firstStrUp + nameSub;
    }

    /**
     * 判断一个类是JAVA类型还是用户定义类型
     *
     * @param clazz
     * @return
     */
    public static boolean isJavaClass(Class<?> clazz) {
        return clazz != null && clazz.getClassLoader() == null;
    }
}
